#include "params.hpp"
#include "channel.h"
#include "dirs.hpp"
#include "force-dir.hpp"
#include "lines.hpp"
#include "tick.h"
#include "udp.h"
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

extern UdpClient* g_uc;

namespace Trade {
void
ParamsMem::set_mem_data(const Params* addr, const int64_t vo_size) noexcept
{
  size = vo_size;
  vo_data = (Params*)(addr);
  sidx = vo_data[0].idx__;
  eidx = vo_data[size - 1].idx__;
}

int
ParamsMem::read_dump_file(const char* dum_filepath) noexcept
{
  FILE* fp = fopen(dum_filepath, "rb");
  if (!fp)
    return errno;

  fseek(fp, 0, SEEK_END);
#ifdef _LINUX_
  int64_t fsize = ftello64(fp);
#endif
#ifdef _MACOS_
  int64_t fsize = ftello(fp);
#endif

  fseek(fp, 0, SEEK_SET);
  size = fsize / SIZE_OF_ONE_BLOCK;

  vo_data = new Params[size];
  auto ptr = vo_data;

  int ret = 0;
  for (int64_t i = 0; i < size; ++i, ++ptr) {
    if (ptr->read_data_(fp)) {
      ret = errno;
      break;
    }
  }
  fclose(fp);

  if (!ret) {
    sidx = vo_data[0].idx__;
    eidx = vo_data[size - 1].idx__;
  }

  // printf("SS %ld EE %ld\n", ss_idx__, ee_idx__);
  return ret;
}

int
Params::read_force(FILE* fp) noexcept
{
  force.read_data();
  return (0);
}

#define READ_DUMP_VAR(var)                                                     \
  if (!ret) {                                                                  \
    if (1 != fread(&(var), sizeof((var)), 1, fp))                              \
      ret = errno;                                                             \
  }


// static usize_t siz = 0;

int
Params::read_data_(FILE* fp) noexcept
{
  int ret = 0;

  READ_DUMP_VAR(idx__);
  READ_DUMP_VAR(t_now_);
  READ_DUMP_VAR(dir_bear);
  READ_DUMP_VAR(dir_ced);
  READ_DUMP_VAR(dir_important_turn);
  READ_DUMP_VAR(dir_turns_space);
  READ_DUMP_VAR(dir_explicit);
  READ_DUMP_VAR(dir_trend);
  READ_DUMP_VAR(dir_rush);
  READ_DUMP_VAR(event);

  READ_DUMP_VAR(dir_force);
  READ_DUMP_VAR(dir_vision_with_space);
  READ_DUMP_VAR(dir_vision);
  READ_DUMP_VAR(old_vision);
  READ_DUMP_VAR(pre_vision);
  READ_DUMP_VAR(dir_big_needle);
  READ_DUMP_VAR(dir_mda);
  READ_DUMP_VAR(dir_virtual);
  READ_DUMP_VAR(dir_opportunity);
  READ_DUMP_VAR(d_dynamical);
  READ_DUMP_VAR(d2_dynamical);

  READ_DUMP_VAR(pct_oppo);

  READ_DUMP_VAR(dir_tjm);
  READ_DUMP_VAR(dt_dir_tjm);

  READ_DUMP_VAR(ddt_force);

  READ_DUMP_VAR(reached_current_line);
  READ_DUMP_VAR(reached_golden_pnt);
  READ_DUMP_VAR(hp_local_min);
  READ_DUMP_VAR(hp_local_max);
  // READ_DUMP_VAR(reverse_space);

  READ_DUMP_VAR(lines);
  // READ_DUMP_VAR(better_lines);
  READ_DUMP_VAR(lgp);
  READ_DUMP_VAR(wave);
  READ_DUMP_VAR(dirs);
  READ_DUMP_VAR(wfw);
  // READ_DUMP_VAR(d5);
  READ_DUMP_VAR(nft);
  // READ_DUMP_VAR(em_up);
  // READ_DUMP_VAR(em_dn);
  // READ_DUMP_VAR(pdc);
  // READ_DUMP_VAR(edir);
  READ_DUMP_VAR(cl);
  READ_DUMP_VAR(mma);
  READ_DUMP_VAR(mda);
  READ_DUMP_VAR(mm_2h);
  READ_DUMP_VAR(bn);

  READ_DUMP_VAR(oppor_trend);

  READ_DUMP_VAR(flv_79);
  // READ_DUMP_VAR(fd);
  // READ_DUMP_VAR(dd3_bd);
  // READ_DUMP_VAR(dd3_pdc);
  // READ_DUMP_VAR(dd3_em);
  // READ_DUMP_VAR(dd3_ft);
  // READ_DUMP_VAR(dd3_ced);
  READ_DUMP_VAR(dd3_sd);
  READ_DUMP_VAR(dd3_exp);
  READ_DUMP_VAR(df3_near_79);
  READ_DUMP_VAR(df3_far_79);
  READ_DUMP_VAR(df3_near_rt);
  READ_DUMP_VAR(df3_far_rt);
  READ_DUMP_VAR(ds3_near);
  READ_DUMP_VAR(ds3_far);
  // READ_DUMP_VAR(fl3);
  // READ_DUMP_VAR(dd3_vs);
  // READ_DUMP_VAR(dd3_force);
  // READ_DUMP_VAR(dd3_virtual);
  READ_DUMP_VAR(ttttt);

  READ_DUMP_VAR(pls_up);
  READ_DUMP_VAR(pls_dn);
  READ_DUMP_VAR(bor);
  // READ_DUMP_VAR(os);
  READ_DUMP_VAR(os2);
  READ_DUMP_VAR(fs_near);
  READ_DUMP_VAR(fs_far);


  // if (!siz) {
  //   auto size = ftell(fp);
  //   printf("bock size = %ld\n", size);
  //   siz = size;
  // }

  return ret;
}

#define DURATION(t) (int(t_now_ - (t)) / 60)
#define TO_MINUTES(tm) ((tm) ? (int((tm)-t_now_) / 60) : 0)

int
Params::show_params(const time_t ts, const price_t hp_now) noexcept
{
  if (ts != t_now_) {
    printf("Err Err %ld, %ld\n", ts, t_now_);
    return -1;
  }
  // printf("\n\033[38;5;46mPARAMS\033[0m : g%ld %ld t %ld %.02lf\n",
  //        idx__,
  //        idx__,
  //        t_now_,
  //        hp_now);

  lines.print(idx__, hp_now);
  // dirs.print(t_now_, wave.dir, idx__);
  wave.print(dirs.mid_dir, dirs.ein_dir, idx__);
  wfw.print(t_now_, hp_now);

  // F+3090 -12428; E+0 -1015; PDC(-1,-1 -1,-1)

  // ttttt.print(idx__, t_now_, hp_now);
  // printf("\033[38;5;168m  ~~~~~~~~ The Dir = %d ~~~ U %d %d mins D "
  //        "%d %d mins ~~~\033[0m\n",
  //        dir_ced,
  //        TO_MINUTES(get_strong_timer___(1)),
  //        TO_MINUTES(get_weak_timer___(1)),
  //        TO_MINUTES(get_strong_timer___(-1)),
  //        TO_MINUTES(get_weak_timer___(-1)));

  printf("\033[38;5;46mSPACE%d(%d %dm %.02lf; %d %dm %.02lf; %d %dm); ExP "
         "%d(%d %dm %.02lf; %d %dm %.02lf; %d %dm)\033[0m\n",
         dir_turns_space,
         dd3_sd.get_first_dir(),
         dd3_sd.get_first_durantion(t_now_),
         (hp_now - dd3_sd.get_first_hp()),
         dd3_sd.get_second_dir(),
         dd3_sd.get_second_durantion(),
         (hp_now - dd3_sd.get_second_hp()),
         dd3_sd.long_dir,
         DURATION(dd3_sd.long_tm),
         dir_explicit,
         dd3_exp.get_first_dir(),
         dd3_exp.get_first_durantion(t_now_),
         (hp_now - dd3_exp.get_first_hp()),
         dd3_exp.get_second_dir(),
         dd3_exp.get_second_durantion(),
         (hp_now - dd3_exp.get_second_hp()),
         dd3_exp.long_dir,
         DURATION(dd3_exp.long_tm));

  printf("Nft: %d %d, %ld, %ld | ",
         nft.d_near,
         nft.d_far,
         (nft.t1 - t_now_) / 60,
         (nft.t2 - t_now_) / 60);
  printf("Force Near %d %d; Far %d %d\n",
         flv_79.d_near_10,
         flv_79.d_near_rt, // flv_79.d_usdt_rt
         flv_79.d_far_10,
         flv_79.d_far_rt // flv_79.d_usd_rt
  );
  ds3_near.print("Trend Near", t_now_, hp_now);
  ds3_far.print("Far", t_now_, hp_now);
  printf("\n");
  df3_near_79.print("FN", t_now_, hp_now);
  df3_near_rt.print("FNL", t_now_, hp_now);
  df3_far_79.print("FF", t_now_, hp_now);
  df3_far_rt.print("FFL", t_now_, hp_now);
  printf("\n");

  // print_channel(&cl);
  printf(
    "#### %d <-%d %d ## dma4 %d %d %d %d ** %d %d %d %d ** %d %d %d %d MAX "
    "%.02f %.02f %.02f %.02f MIN %.02f %.02f %.02f %.02f ####\n",
    dir_vision,
    old_vision,
    pre_vision,
    mda[0],
    mda[1],
    mda[2],
    mda[3],
    bn[0],
    bn[1],
    bn[2],
    bn[3],
    ((mma[0].tm_max >= mma[0].tm_min) ? 1 : -1),
    ((mma[1].tm_max >= mma[1].tm_min) ? 1 : -1),
    ((mma[2].tm_max >= mma[2].tm_min) ? 1 : -1),
    ((mma[3].tm_max >= mma[3].tm_min) ? 1 : -1),
    hp_now - mma[0].hp_max,
    hp_now - mma[1].hp_max,
    hp_now - mma[2].hp_max,
    hp_now - mma[3].hp_max,
    hp_now - mma[0].hp_min,
    hp_now - mma[1].hp_min,
    hp_now - mma[2].hp_min,
    hp_now - mma[3].hp_min);
  // opportunity.print();
  print_reached("Lines", 0, reached_current_line, idx__);
  print_reached("Golden", 0, lgp.reached, idx__);
  const dir_t d = oppor_trend.get_oppor_dir();
  // force.print();

  printf("--------|| Near ||--------\n");
  fs_near.print(hp_now, t_now_);
  printf("--------|| Far ||--------\n");
  fs_far.print(hp_now, t_now_);

  printf("______%d %d %d _______OARANS : g%ld %ld t %ld %.02lf\n\n",
         d,
         oppor_trend.oppor_up,
         oppor_trend.oppor_dn,
         idx__,
         idx__,
         t_now_,
         hp_now);

  return (0);
}

#define TIMER(tm) ((tm) + t_now_)

time_t
Params::weak_timer(const dir_t d) noexcept
{
  return ttttt.get_weak_timer(d, t_now_);
}

time_t
Params::strong_timer(const dir_t d) noexcept
{
  return ttttt.get_strong_timer(d, t_now_);
}

time_t
Params::get_weak_timer(const dir_t d) noexcept
{
  return wfw.get_weak_timer(wave, dirs, d, t_now_);
}

time_t
Params::get_strong_timer(const dir_t d) noexcept
{
  return wfw.get_timer(wave, d, t_now_);
}

time_t
Params::get_abs_weak_timer(const dir_t d) noexcept
{
  return wfw.get_abs_weak_timer(wave, dirs, d, t_now_);
}

time_t
Params::get_abs_strong_timer(const dir_t d) noexcept
{
  return wfw.get_abs_timer(wave, d, t_now_);
}

time_t
Params::weak_timer___(const dir_t d) noexcept
{
  time_t t = ttttt.get_weak_timer(d, t_now_);
  if ((t < t_now_ + 300) || (t > t_now_ + 90 * 60))
    t = TIMER(MINUTES_45);

  return t;
}

time_t
Params::strong_timer___(const dir_t d) noexcept
{
  time_t t = ttttt.get_strong_timer(d, t_now_);
  if (t < t_now_ + 300)
    t = TIMER(MINUTES_45);
  // t = get_strong_timer___(d);
  return t;
}

time_t
Params::get_weak_timer___(const dir_t d) noexcept
{
  time_t timer = wfw.get_weak_timer(wave, dirs, d, t_now_);

  if (timer <= t_now_)
    timer = TIMER(MINUTES_45);

  return timer;
}

time_t
Params::get_strong_timer___(const dir_t d) noexcept
{
  time_t timer = wfw.get_timer(wave, d, t_now_);
  if (!timer || timer < t_now_ + MINUTES_45)
    timer = TIMER(MINUTES_45);
  return timer;
}

Params*
ParamsMem::get_data_by_index(const int64_t idx) const noexcept
{
  if (idx >= sidx && idx <= eidx) {
    return vo_data + (idx - sidx);
  }
  return nullptr;
}

void
ParamsMem::data_idx(const int64_t idx, const price_t hp_now) noexcept
{
  this->idx = idx;
  vo_ = get_data_by_index(idx);
  t_now = vo_->t_now_;
  this->hp_now = hp_now;
}

void
ParamsMem::release() noexcept
{
  if (vo_data) {
    delete[] vo_data;
    vo_data = nullptr;
  }
}

bool
Params::have_4_force_dirs() const noexcept
{
  return (df3_far_79.get_first_dir() && df3_far_rt.get_first_dir() &&
          df3_near_79.get_first_dir() && df3_near_rt.get_first_dir());
}

dir_t
Params::get_rush_dir() const noexcept
{
  dir_t dir_exp = dd3_exp.get_first_dir();
  if (dir_exp) {
    if ((df3_far_79.get_first_dir() == df3_far_rt.get_first_dir() &&
         dir_exp == df3_far_79.get_first_dir()) ||
        (df3_near_79.get_first_dir() == df3_near_rt.get_first_dir() &&
         dir_exp == df3_near_79.get_first_dir())) {
      return (dir_explicit != -dir_exp) ? dir_exp : 0;
    } else {
      return (0);
    }
  }

  return (0);
}

bool
Params::is_consistent_dir(const dir_t d) const noexcept
{
  return (d == nft.d_near || dirs.big_dir != -d);
}

bool
Params::big_same_near() const noexcept
{
  return (dirs.big_dir && dirs.big_dir == nft.d_near);
}

int
Params::send_cur_pos() noexcept
{
  if (!g_uc)
    return (0);

  static const char* json = "{\"i\":%ld, \"t\":0}";
  char buf[512];
  snprintf(buf, 512, json, idx__);
  return udpc_send_message(g_uc, buf);
}

int
Params::send_important_dirs() noexcept
{
  if (!g_uc)
    return (0);

  static const char* json = "{\"i\":%ld, \"t\":1, \"exp\":%d, \"intent\":%d, "
                            "\"tsd\":%d, \"ftsd\":%d, \"solved\":%d, \"b\":%d}";

  DIR_DREAMER_TYPE ddt_exp = dd3_exp.retrive_dir_type(dir_explicit, t_now_);
  DIR_DREAMER_TYPE ddt_ftsd = ttttt.get_first_dir_type();
  DIR_DREAMER_TYPE ddt_tsd = dd3_sd.retrive_dir_type(dir_turns_space, t_now_);
  char buf[512];
  snprintf(buf,
           512,
           json,
           idx__,
           ddt_exp,
           ddt_force,
           ddt_tsd,
           ddt_ftsd,
           nft.d_near,
           dirs.big_dir);

  return udpc_send_message(g_uc, buf);
}

int
load_data_from_shared_memory(const char* mem_key,
                             TICK_DATA* td,
                             size_t* td_size,
                             Params** vo,
                             size_t* vo_size) noexcept
{
  size_t size = 0;
  int64_t size1 = 0;
  int64_t size2 = 0;

  int fd = shm_open(mem_key, O_RDONLY, S_IRUSR | S_IWUSR);
  if (fd == -1) {
    perror("open");
    return -1;
  }

// map shared memory to process address space
#ifdef _LINUX_
  char* addr = (char*)mmap64(
    NULL, sizeof(int64_t) + sizeof(int64_t), PROT_READ, MAP_SHARED, fd, 0);
#endif

#ifdef _MACOS_
  char* addr = (char*)mmap(
    NULL, sizeof(int64_t) + sizeof(int64_t), PROT_READ, MAP_SHARED, fd, 0);
#endif

  if (addr == MAP_FAILED) {
    perror("mmap");
    return -2;
  }

  size1 = *((int64_t*)(addr));
  size2 = *((int64_t*)(addr + sizeof(int64_t)));
  munmap(addr, sizeof(int64_t) + sizeof(int64_t));
  size = size1 + size2;

#ifdef _LINUX_
  addr = (char*)mmap64(NULL,
                       size + sizeof(int64_t) + sizeof(int64_t),
                       PROT_READ,
                       MAP_SHARED,
                       fd,
                       0);
#endif

#ifdef _MACOS_
  addr = (char*)mmap(NULL,
                     size + sizeof(int64_t) + sizeof(int64_t),
                     PROT_READ,
                     MAP_SHARED,
                     fd,
                     0);
#endif

  if (addr == MAP_FAILED) {
    perror("mmap");
    unlink(mem_key);
    return -2;
  }

  *td = (TICK_DATA)(addr + sizeof(int64_t) + sizeof(int64_t));
  *td_size = size1 / sizeof(Tick);

  *vo = (Params*)((*td) + (*td_size));
  *vo_size = size2 / sizeof(Params);

  return (0);
}

int
release_shared_memory(const char* mem_key,
                      const void* data,
                      const size_t td_size,
                      const size_t vo_size)
{
  munmap((char*)(data) - sizeof(size_t) - sizeof(size_t),
         (td_size * sizeof(Tick) + vo_size * sizeof(Params) + sizeof(size_t) +
          sizeof(size_t)));
  unlink(mem_key);

  return (0);
}

Params*
get_vo_by_index(const Params* vo,
                const size_t vo_size,
                const int64_t idx) noexcept
{
  if (!vo || !vo_size)
    return nullptr;

  if (idx < vo[0].idx__ || idx >= (int64_t)(vo_size)) {
    int64_t pos = idx - vo[0].idx__;
    return (Params*)(vo) + pos;
  }
  return nullptr;
}

bool
Params::space_opposite_tom(const dir_t d) const noexcept
{
  return (d && dir_vision_with_space == -d &&
          dir_vision_with_space == nft.d_near);
}

DIR_DREAMER_TYPE
Params::retrive_vision_dir_type() const noexcept
{
  DIR_DREAMER_TYPE ddt = DIR_DREAMER_TYPE::DD_UNKNOW;
  if (dir_vision) {
    ddt = (dir_vision > 0) ? DIR_DREAMER_TYPE::DD_UP
                           : (old_vision < 0 ? DIR_DREAMER_TYPE::DD_DN
                                             : DIR_DREAMER_TYPE::DD_UNKNOW);
  } else {
    ddt = (old_vision > 0) ? DIR_DREAMER_TYPE::DD_UP_SWING
                           : (old_vision < 0 ? DIR_DREAMER_TYPE::DD_DN_SWING
                                             : DIR_DREAMER_TYPE::DD_UNKNOW);
  }
  return ddt;
};

price_t
Params::get_inflection(const dir_t d) const noexcept
{
  return (d < 0) ? mma[3].hp_max : mma[3].hp_min;
}

dir_t
Params::predicted_near_dir() const noexcept
{
  return nft.d_near;
}

dir_t
Params::predicted_far_dir() const noexcept
{
  return nft.d_far;
}

dir_t
Params::force_near_dir() const noexcept
{
  return (flv_79.d_near_rt == flv_79.d_near_10) ? flv_79.d_near_rt : 0;
}

dir_t
Params::force_far_dir() const noexcept
{
  return (flv_79.d_far_rt == flv_79.d_far_10) ? flv_79.d_far_rt : 0;
}

dir_t
Params::get_mda_dir() const noexcept
{
  dir_t d = mda[0];
  for (int i = 1; i < MAX_MA_DIR; ++i) {
    if (d != mda[i])
      return (0);
  }
  return d;
}

void
Params::get_mda_percent(int& up, int& dn) const noexcept
{
  up = dn = 0;
  for (int i = 1; i < MAX_MA_DIR; ++i) {
    if (mda[i] > 0)
      up += 1;
    else if (mda[i] < 0)
      dn += 1;
  }
  up = int(double(up) / double(MAX_MA_DIR) * 100);
  dn = int(double(dn) / double(MAX_MA_DIR) * 100);
}

bool
Params::confused_predict_near_or_far() const noexcept
{
  return (ds3_near.is_swing_dir(t_now_) || ds3_far.is_swing_dir(t_now_));
}

bool
Params::consistent_visual_dir(const dir_t d) const noexcept
{
  if (get_mda_dir() && get_mda_dir() != d)
    return false;
  else
    return true;
}

bool
Params::dangerous_dir(const dir_t d) const noexcept
{
  case_t cno = 0;
  if ((-d == dir_vision || (-d == old_vision && !dir_vision)) &&
      -d == pre_vision) {
    cno |= 1;
  }

  if (get_mda_dir() != d && force_near_dir() == -d &&
      ddt_to_dir(ddt_force) == -d) {
    cno |= 2;
  }

  if (d != dir_vision && ddt_to_dir(ddt_force) == -d && dir_turns_space != d &&
      ttttt.tt_1.dir != d) {
    cno |= 4;
  }

  return (cno != 0);
}

} // namespace Trade
